package com.atguigu.gulimall.order.service.impl;

import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.gulimall.order.constant.OrderConstant;
import com.atguigu.gulimall.order.feign.CartFeignService;
import com.atguigu.gulimall.order.feign.MemberFeignService;
import com.atguigu.gulimall.order.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.order.to.OrderCreateTo;
import com.atguigu.gulimall.order.vo.*;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
//import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.order.dao.OrderDao;
import com.atguigu.gulimall.order.entity.OrderEntity;
import com.atguigu.gulimall.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.thymeleaf.templateresolver.StringTemplateResolver;

import javax.annotation.Resource;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

//    private ThreadLocal<>

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private ThreadPoolExecutor threadPoolExecutor;

    @Resource
    MemberFeignService memberFeignService;

    @Resource
    private CartFeignService cartFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();
        MemberRespVo memberRespVo = LoginUserInterceptor.local.get();
        //下面的代码是为了解决多线程下ThreadLocal数据不共享的问题
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();


        //1.远程查询会员服务所有的地址列表,那么memberId在哪里呢，我们可以从拦截器当中获取
        CompletableFuture<Void> getAddressTask = CompletableFuture.runAsync(() -> {
            //在执行真正的业务之前，先将主线程的请求上下文数据保存到新线程的ThreadLocal中。否则拦截器中没有数据会报空指针
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
            orderConfirmVo.setAddress(address);
        }, threadPoolExecutor);


        //2.远程查询购物车中选中的购物项
        CompletableFuture<Void> cartTask = CompletableFuture.runAsync(() -> {
            //在执行真正的业务之前，先将主线程的请求上下文数据保存到新线程的ThreadLocal中。否则拦截器中没有数据会报空指针
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
            orderConfirmVo.setItems(currentUserCartItems);
        }, threadPoolExecutor);

        //3.查询用户积分
        Integer integration = memberRespVo.getIntegration();
        orderConfirmVo.setIntegration(integration);
        //4.去优惠券系统查询优惠券信息，我们就不做了。。。。。


        CompletableFuture.allOf(getAddressTask,cartTask).get();
        //TODO 5.防重令牌(幂等性)
        String token = UUID.randomUUID().toString().replace("-", "");
        orderConfirmVo.setOrderToken(token);//给页面一个令牌
        stringRedisTemplate.opsForValue().set(OrderConstant.USER_ORDER_PREFIX+memberRespVo.getId(),token,30, TimeUnit.MINUTES);
        //可想而知，我们在生成订单确认页的时候给页面存放一个令牌，我们的方法是将令牌存放到确认页的一个隐藏的input框里面
        //等到点击提交订单后，后台便发起一个请求，携带上上次放在页面里的令牌作为参数，进而行进防重校验，但这些已经不是本次生成订单确认页的事情了
        return orderConfirmVo;
    }

    /**
     * //去创建订单、验令牌、锁库存。。。
     * @param orderSubmitVo
     * @return
     */
//    @GlobalTransactional
    @Transactional
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
        SubmitOrderResponseVo submitOrderResponseVo = new SubmitOrderResponseVo();
        MemberRespVo memberRespVo = LoginUserInterceptor.local.get();
        String orderToken = orderSubmitVo.getOrderToken();
        //1.验证令牌,必须保证关于令牌的获取、对比和删除是整个原子性的，为此我们写一段lua脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        //对于LUA脚本的执行只有两种返回结果，不是零，就是一，因此下面的对象泛型是Long类型
        /**
         * execute(RedisScript<T> script,
         *          List<K> keys, redis中的数据的key
         *          Object... args  redis中的数据跟谁比较呢？当然是跟浏览器传来的令牌比较了
         */
        //原子的获取和验证令牌
        Long execute = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(OrderConstant.USER_ORDER_PREFIX + memberRespVo.getId()), orderToken);
        //对比是否删除令牌
        if(execute == 1l){
            //令牌验证成功
            //去创建订单、锁库存
            OrderCreateTo order = createOrder();
            //验价，不做了

        }else {
            //令牌验证失败，就不用往下做了
            submitOrderResponseVo.setCode(1);      //状态码设置为1说明调用失败
            return submitOrderResponseVo;
        }

        return null;
    }


    /**
     * 此方法专门用于创建一个订单
     * @return
     */
    private OrderCreateTo createOrder(){
        OrderCreateTo orderCreateTo = new OrderCreateTo();
        //1.生成一个订单号
        String orderSn = IdWorker.getTimeId();
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderSn);
        return orderCreateTo;
    }

}